这次说的是如何通过kuberntes API 访问诸如pods , services, deployment等等对象和资源
kuberntes 的编程接口主要由k8s.io/client-go
库组成 , client-go是一个典型的web服务客户端库, 它支持REST verbs, 比如: create , get, list ,update,delete,patch , 并且还支持watch
clinet-go在GitHub的地址是https://github.com/kubernetes/client-go , 它对kuberntes版本是一一对应的
它的对应表是:
Kubernetes 1.9 | Kubernetes 1.10 | Kubernetes 1.11 | Kubernetes 1.12 | Kubernetes 1.13 | Kubernetes 1.14 | Kubernetes 1.15 | |
---|---|---|---|---|---|---|---|
client-go 6.0 | ✓ | +- | +- | +- | +- | +- | +- |
client-go 7.0 | +- | ✓ | +- | +- | +- | +- | +- |
client-go 8.0 | +- | +- | ✓ | +- | +- | +- | +- |
client-go 9.0 | +- | +- | +- | ✓ | +- | +- | +- |
client-go 10.0 | +- | +- | +- | +- | ✓ | +- | +- |
client-go 11.0 | +- | +- | +- | +- | +- | ✓ | +- |
client-go 12.0 | +- | +- | +- | +- | +- | +- | ✓ |
client-go HEAD | +- | +- | +- | +- | +- | +- | +- |
其中
- ✓ 表示clinet-go和kuberntes都有相同的功能和相同的 API 组版本
- + 表示clinet-go具有 Kubernetes 集群中可能缺少的特性或 API 组版本。 这可能是因为在 client-go 中添加了功能,或者因为 Kubernetes 删除了旧的、不推荐的功能。 但是,它们的所有共同点(即大多数 api)都可以工作
- - 表示clinet-go与kuberntes不兼容
从上表看client-go 1.0为 Kubernetes 1.4发布,现在为 Kubernetes 1.15发布了 client-go 12.0 , 看规律是相差了3
API Machinery
还有一个库也是kuberntes开发中经常用到的, 就是API Machinery , 它是实现了kuberntes api所有通过构建块
创建和使用client
下面的代码演示了如何使用client-go
1 | import ( |
我们从
clientcmd.BuildConfigFromFlags
读取和解析kubeconfig从
clientcmd.BuildConfigFromFlags
中 ,我们得到一个rest.Config
, 什么是rest.config
,你可以从k8s.io/client-go/rest这个包里看到将
rest.Config
传递给kubernetes.NewForConfig
,这是为了得到一个实际的Kubernetes client set, 它之所以叫client set , 是因为它包含了kuberntes资源的多种client当我们在集群内运行pod的时候, kubelet将自动的把
/var/run/secrets/kubernetes.io/serviceaccount
挂载为一个service account, 它取代了刚刚提到的kubecofig file, 并且由rest.InClusterConfig()
这个方法转化成了rest.Config
,你经常会发现以下 rest.InClusterConfig ()和 clientcmd 的组合。 包括对 KUBECONFIG 环境变量的支持:1
2
3
4
5
6
7
8
9
10
11
12
13config, err := rest.InClusterConfig()
if err != nil {
// fallback to kubeconfig
kubeconfig := filepath.Join("~", ".kube", "config")
if envvar := os.Getenv("KUBECONFIG"); len(envvar) >0 {
kubeconfig = envvar
}
config, err = clientcmd.BuildConfigFromFlags("", kubeconfig)
if err != nil {
fmt.Printf("The kubeconfig cannot be loaded: %v\n", err)
os.Exit(1)
}
}之后我们选择core组v1 version 去一个叫examle的namespace中访问一个名为book的pod, 即以下代码
1
pod, err := clientset.CoreV1().Pods("book").Get("example",metav1.GetOptions{})
上述代码中,我们用metav1的getoptions方法去访问API Server , 其中corev1 , pods ,namespa这些变量也随之get调用送到了server (这通常被叫做为builder pattern)
之后这个Get call用HTTP GET方法去请求
/api/v1/namespaces/book/pods/example
, 如果server回应了200响应码 , 那么在response中会携带经过编码的pod对象 , 或者是json
Kubernetes Objects
从系统的角度来说, kuberntes用go interface实现了kuberntes objects , 它被叫做runtime.Object
它定义在k8s.io/apimachinery/pkg/runtime
, 实际上看上去很简单
1 | // Object interface must be supported by all API types registered with Scheme. |
其中schema.ObjectKind(from the k8s.io/apimachinery/pkg/runtime/schema package)也是一个简单的接口
1 | // All objects that are serialized from a Scheme encode their type information. |
也就是说kuberntes object在go中是一个数据结构
返回并设置GroupVersionKind
deep-copied 深度复制
深层副本是数据结构的克隆,它不与原始对象共享任何内存。 只要代码必须在不修改原始对象的情况下修改对象,就可以使用它
TypeMeta
从上面我们可以看到runtime.Object就是一个go interface , 通过kuberntes/apimachinery/pkg/apis/meta/v1/types.go中定义的TypeMeta镶嵌进kuberntes类型中
1 | // TypeMeta describes an individual object in an API response or request |
一个pod的类型声望看起来像这样
1 | type Pod struct { |
ObjectMeta
除了 TypeMeta 之外,大多数顶级对象都有一个 metav1.ObjectMeta 类型的字段,同样来自于 k8s.io/apimachinery/pkg/meta/v1文件包
1 | type ObjectMeta struct { |
在yaml文件的编写过程中,他们对应的是metadata字段
1 | metadata: |
spec和status
最后,几乎每个顶级对象都有 spec 和 status 部分。 这个约定来自 Kubernetes API 的声明性质: spec 是用户的愿望,而 status 是这个愿望的结果
Informers and Caching
上面的clinet set也包括了watch方法,它提供了一个对对象的更改(添加、删除、更新)做出反应的事件接口 , informer提供了更高级的接口, 农村缓存和索引 , 这个为了减轻 api服务器的压力
informers的工作逻辑:
- 从API Server获取输入
- 用类似与client 接口相似的lister从内存中获取和列出对象
- 为add,removes,updates注册event handler
- 用strore这种结构实现内存缓存
每种特定的类型都会有特定的informer 也就是说每个GVR都会有对应的informer , 为了更容易的共享informer, 我们用shared informer factory来实例化shared informer , shared informer可以允许我们能共享同一类资源, 在实际开发中我们用shared informer factory来初始化informer
从一个REST config开始, 我们可以很容易用client set来创建一个shared informer factory ,
1 | import ( |
上面展示了如何如何创建一个pod的informer
另外informer还可以添加几个事件(add , update delete)的handler , 这些通常是为了触发控制器的业务逻辑(这个之后再讲)
其实informer是控制器循环的重中之重, 这里只是先简单介绍下, 之后我会再很详细的说明, 这里先有个概念即可
Work Queue
工作队列是一种数据结构, 你可以按照队列预定义的顺序添加元素和从队列中提取元素 , 这种队列被称为priority queue , client-go用k8s.io/client-go/util/workqueue来实现
实现的接口如下:
1 | type Interface interface { |
上述中Add(item)会添加一个项, 之后Len()会获取其长度, Get ()返回一个具有最高优先级的项(并阻塞直到有一个可用)。 Get ()返回的每个项在控制器完成处理后都需要一个 Done (item)调用。 同时,一个重复的 Add (item)只会将该项标记为 dirty,以便在调用 Done (item)时重新读取该项
下面的队列类型是从这个通用接口派生的
Delayinginterface 可以在以后添加一个项。 这使得在失败之后重新查找项目变得更加容易,而不会陷入热循环
1 | type DelayingInterface interface { |
Ratelimitinginterface 速率限制添加到队列中的项。 它扩展了 DelayingInterface:
1 | type RateLimitingInterface interface { |
深入API Machinery
API Machinery是核心就是kind
API machine 库中的一个核心术语是 GroupVersionKind,简称 GVK
在go中,每个 GVK 对应一种go type。 相比之下,Go type可以属于多个 gvk
kind的格式通常是单数 , 对于 CustomResourceDefinition 类型,它必须是 DNS 路径标签(RFC 1035)
每个 GVR 对应一个 HTTP 路径。 Gvrs 用于标识 kubernettes API 的 REST 端点, 比如GVR apps/v1.deployments
映射到/apis/apps/v1/namespaces/namespace/deployments
client使用此映射构造 HTTP 路径来访问 GVR
如何分辨一个资源是集群范围的还是名称空间中的呢
在HTTP路径上有namespace的就是属于名称空间内的, 如果没有就是属于集群范围内的 ,比如rbac.authorization.k8s.io/v1.clusterroles就是集群范围内的 , 可以在apis/rbac.authorization.k8s.io/v1/clusterroles中访问到
资源通常是用小写和复数表示, 而且必须符合DNS路径规范(RFC 1025)
###REST Mapping
将GVK映射到GVR的过程叫做REST mapping
RESTMapper是个Golang interface, 它的作用就是通过GVK请求GVR
1 | RESTMapping(gk schema.GroupKind, versions ...string) (*RESTMapping, error) |
1 | type RESTMapping struct { |
此外这个接口还提供许多便利的功能
1 | // KindFor takes a partial resource and returns the single match. |
Restmapper 接口有多个不同的实现。 对于客户端应用程序来说,最重要的一个功能就是 k8s.io/client-go/restmapper 包中基于发现的 DeferredDiscoveryRESTMapper: 它使用来自 Kubernetes API 服务器的发现信息来动态地构建 REST 映射。 它还可以与非核心资源(如定制资源)一起工作。
Scheme
最后一个概念就是来自k8s.io/apimachinery/pkg/runtime中的scheme , 它可以将kuberntes中的GVK这些概念联系起来 , 它的作用就是将go type映射到GVK中
1 | func (s *Scheme) ObjectKinds(obj Object) ([]schema.GroupVersionKind, bool, error) |
在上面的kuberntes objects小节中说了 , 一个object有个属性就是GetObjectKind() , 类型是schema.ObjectKind
该包可以通过反射获取给定对象的 Golang 类型,并将其映射到已注册的 Golang 类型的 GVK中,
使用以下的函数可以达成目的
1 | scheme.AddKnownTypes(schema.GroupVersionKind{"", "v1", "Pod"}, &Pod{}) |
当然对于kuberntes核心的类型还说, schema已经注册好了已经有的类型在client-go client set中预先注册好的类型 , 实际上每个由client-gen code generator生成的client set中都有一个所有type所有group所有version的一个子包
总结一下, api machinery包的概念和作用是如下所示